package com.crois.barrier.web.loadbalance.strategy;

import com.alibaba.nacos.api.naming.pojo.Instance;
import com.crois.barrier.common.dto.ApiRouteInfo;
import com.crois.barrier.common.dto.BarrierClientInstance;
import com.crois.barrier.common.enums.UpstreamStrategyEnum;
import com.crois.barrier.common.exception.BarrierException;
import com.crois.barrier.common.exception.BarrierExceptionEnum;
import com.crois.barrier.web.loadbalance.rule.LoadBalanceRule;
import com.crois.barrier.web.loadbalance.rule.NacosLoadBalancerByServiceName;
import com.crois.barrier.web.loadbalance.rule.WeightRoundRobinLoadBalanceRule;
import com.crois.barrier.web.spring.SpringBeanUtils;
import com.crois.barrier.web.support.ServerWebExchangeUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static com.crois.barrier.web.support.ParamMappingUtils.queryMapToString;

/**
 * @author Hou Ze Yu
 * @description serviceName 转发 策略
 * @date 2020/7/20
 */
public class ServiceNameDivideStrategy implements DivideStrategy{

    Logger LOGGER = LoggerFactory.getLogger(ServiceNameDivideStrategy.class);

    private static final String BARRIER_VERSION_VALUE = "zmd-version";

    @Override
    public void divide(ApiRouteInfo apiRouteInfo, ServerWebExchange webExchange) {
        LOGGER.info("[begin invoke ServiceNameDivide strategy]");

        if (apiRouteInfo==null || !apiRouteInfo.getUpstreamStrategy().equals(UpstreamStrategyEnum.SERVICE_NAME_ROUTE.getName())){
            throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_FAIL);
        }
        String serviceName = apiRouteInfo.getServiceName();
        if (StringUtils.isNotEmpty(serviceName)){
            NacosLoadBalancerByServiceName nacosLoadBalancerByServiceName = SpringBeanUtils.getInstance().getBean(NacosLoadBalancerByServiceName.class);
            if (Objects.isNull(nacosLoadBalancerByServiceName)){
                throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_FAIL);
            }
            String version = webExchange.getRequest().getHeaders().get(BARRIER_VERSION_VALUE)==null?null:webExchange.getRequest().getHeaders().get(BARRIER_VERSION_VALUE).get(0);
            List<Instance> instanceList = nacosLoadBalancerByServiceName.choose(serviceName, version, webExchange);
             if (CollectionUtils.isEmpty(instanceList)){
                 LOGGER.error("[ServiceNameDivide strategy],serviceName:{}, not have available instance",serviceName);
                 throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_NOT_DOING_SERVER_FAIL);
             }else {
                 LoadBalanceRule wightRoundRobinLoadBalanceRule = SpringBeanUtils.getInstance().getBean(WeightRoundRobinLoadBalanceRule.class);

                 List<BarrierClientInstance> doorClientInstanceList = buildInstanceList(instanceList,apiRouteInfo);

                 BarrierClientInstance chooseClientInstance = wightRoundRobinLoadBalanceRule.choose(doorClientInstanceList,webExchange);

                 String requestRrl =  buildRequestUrl(chooseClientInstance,webExchange,apiRouteInfo.getProtocol());
                 if (StringUtils.isNotEmpty(requestRrl)){
                     webExchange.getAttributes().put(ServerWebExchangeUtils.BARRIER_REQUEST_URL_ATTR,requestRrl);
                 }
             }
        }else {
            LOGGER.error("[ServiceNameDivide strategy],not have available service name");
            throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_NOT_DOING_SERVER_NAME_FAIL);
        }
        LOGGER.info("[end invoke ServiceNameDivide strategy]");
    }

    private String buildRequestUrl(BarrierClientInstance chooseClientInstance, ServerWebExchange webExchange, String protocol) {
        String requestPath = webExchange.getRequest().getPath().toString();
        String ip = chooseClientInstance.getIp();
        int port = chooseClientInstance.getPort();
        String requestProtocol = Optional.ofNullable(protocol).orElse("http");
        String url = requestProtocol+"://"+ip+":"+port+requestPath;
        MultiValueMap<String, String> queryParams = webExchange.getRequest().getQueryParams();
        if (queryParams.size()!=0){
            String afterQueryString = queryMapToString(queryParams);
            url= url+"?"+afterQueryString;
        }
        return url;
    }

    private List<BarrierClientInstance> buildInstanceList(List<Instance> instanceList, ApiRouteInfo apiRouteInfo) {

        List<BarrierClientInstance> doorClientInstanceList =new ArrayList<>(instanceList.size());

        if (CollectionUtils.isNotEmpty(instanceList)){
            for (Instance instance : instanceList) {
                URI uri = URI.create(apiRouteInfo.getProtocol()+"://"+instance.getIp());
                int port = instance.getPort();
                String host = uri.getHost();
                int weight = (int) instance.getWeight();
                BarrierClientInstance doorClientInstance=new BarrierClientInstance();
                doorClientInstance.setWeight(weight);
                doorClientInstance.setPort(port);
                doorClientInstance.setIp(host);
                doorClientInstance.setServiceName(apiRouteInfo.getServiceName());
                doorClientInstanceList.add(doorClientInstance);
            }
        }
        return doorClientInstanceList;
    }
}
